package com.andova.glplugin

import android.content.Context
import android.graphics.BitmapFactory
import android.opengl.GLES30
import androidx.annotation.IntRange
import androidx.annotation.RawRes
import com.andova.ext.FLOAT_SIZE_BYTES
import com.andova.ext.getAsset
import com.andova.image.glesInitialize
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer

class GlFrameAnimPlugin(private val context: Context,
                        private val texStorageWidth: Int,
                        private val texStorageHeight: Int,
                        @RawRes private val rawResArray: IntArray,
                        @IntRange(from = 1) private val frameInterval: Int = 1) : GlPluginCallback {
    private var program = -1
    private var rawIndex = 0f
    private val texIds = IntArray(1)
    private var vertexBuffer: FloatBuffer? = null
    private var textureBuffer: FloatBuffer? = null
    private fun allocateVertexBufferIfNeeded() {
        if (vertexBuffer != null) return
        val vertices: FloatArray = floatArrayOf(
                -1f, -1f, 1f,
                1f, -1f, 1f,
                1f, 1f, 1f,
                -1f, 1f, 1f
        )
        vertexBuffer = ByteBuffer.allocateDirect(vertices.size * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertices).apply { position(0) }
    }

    override fun onPluginCreate(width: Int, height: Int) {
        GLES30.glGenTextures(1, texIds, 0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D_ARRAY, texIds[0])
        GLES30.glTexStorage3D(GLES30.GL_TEXTURE_2D_ARRAY, 1, GLES30.GL_RGBA8, texStorageWidth, texStorageHeight, rawResArray.size)
        for ((i, raw) in rawResArray.withIndex()) {
            val instr = context.resources.openRawResource(raw)
            val bmp = try {
                BitmapFactory.decodeStream(instr)
            } catch (e: IOException) {
                null
            } finally {
                try {
                    instr.close()
                } catch (t: IOException) {
                }
            }
            bmp ?: continue
            val pixels = ByteBuffer.allocate(bmp.width * bmp.height * Int.SIZE_BYTES)
            bmp.copyPixelsToBuffer(pixels)
            pixels.position(0)
            GLES30.glTexSubImage3D(
                    GLES30.GL_TEXTURE_2D_ARRAY,
                    0,
                    0,
                    0,
                    i,
                    bmp.width,
                    bmp.height,
                    1,
                    GLES30.GL_RGBA,
                    GLES30.GL_UNSIGNED_BYTE,
                    pixels
            )
            bmp.recycle()
        }

        allocateVertexBufferIfNeeded()

        program = glesInitialize(getAsset(context, "plugins/vert_texture_array.glsl"), getAsset(context, "plugins/frag_texture_array.glsl"))
        GLES30.glUniform1i(GLES30.glGetUniformLocation(program, "uTexArray"), 0)
    }

    override fun onPluginChanged(width: Int, height: Int) {
        onPluginDestroy()
        onPluginCreate(width, height)
    }

    override fun onPluginDraw(x: Int, y: Int, width: Int, height: Int) {
        GLES30.glUseProgram(program)
        GLES30.glViewport(x, y, width, height)
        GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT or GLES30.GL_COLOR_BUFFER_BIT)

        allocateVertexBufferIfNeeded()
        if (rawIndex % frameInterval == 0f) {
            val textures: FloatArray = floatArrayOf(
                    0f, 1f, rawIndex,
                    1f, 1f, rawIndex,
                    1f, 0f, rawIndex,
                    0f, 0f, rawIndex
            )
            textureBuffer = ByteBuffer.allocateDirect(textures.size * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer().put(textures).apply { position(0) }
        }
        if (++rawIndex >= rawResArray.size) rawIndex = 0f

        GLES30.glEnableVertexAttribArray(GLES30.glGetAttribLocation(program, "aPosDraw"))
        GLES30.glEnableVertexAttribArray(GLES30.glGetAttribLocation(program, "aPosTexture"))
        GLES30.glEnable(GLES30.GL_BLEND)
        GLES30.glBlendFunc(GLES30.GL_ONE, GLES30.GL_ONE_MINUS_SRC_ALPHA)

        GLES30.glVertexAttribPointer(GLES30.glGetAttribLocation(program, "aPosDraw"), 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)
        GLES30.glVertexAttribPointer(GLES30.glGetAttribLocation(program, "aPosTexture"), 3, GLES30.GL_FLOAT, false, 0, textureBuffer)

        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D_ARRAY, texIds[0])
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, 4)

        GLES30.glDisable(GLES30.GL_BLEND)
        GLES30.glDisableVertexAttribArray(GLES30.glGetAttribLocation(program, "aPosDraw"))
        GLES30.glDisableVertexAttribArray(GLES30.glGetAttribLocation(program, "aPosTexture"))
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D_ARRAY, GLES30.GL_NONE)
    }

    override fun onPluginDestroy() {
        vertexBuffer?.clear()
        textureBuffer?.clear()
        vertexBuffer = null
        textureBuffer = null
        GLES30.glDeleteTextures(texIds.size, texIds, 0)
    }
}